<!--
现有功能：
  固定表头
  列排序（默认不指定顺序）
  列筛选
  固定列(最多支持复杂表头2层结构，3层及以上会导致样式错位;复杂表头中指定子表头的其中一个字段即可固定整个层级的列)
  自定义列
  复杂表头
  body合并
  分页
  单元格溢出设置
  列宽设置(可在表头中指定,也可在configs.width中指定;建议至少保留一列不设置,避免列宽被自动计算与预期不符)
  单选
  多选
  行展开 (与单选、多选三者互斥，优先级： 单选 > 多选 > 行展开)
  样式调整
  表格事件
  自定义高度
  定制单元格样式
  单元格鼠标选中并键盘移动选中位置(默认关闭)
  默认选中第一行(可配置默认选中哪行,默认第一行)

TODO:
  列宽拖拽
  列宽缓存
  筛选缓存
  头部自定义列
  无数据时固定列失效
-->
<template>
  <div class="full-height full-width mg-easy-table">
    <template v-if="headers.length">
      <ve-table :virtual-scroll-option="virtualScrollOption"
                ref="mg-easy-table-instance"
                :columns="headers"
                :table-data="datas"
                :max-height="maxH"
                :sort-option="sortOption"
                :scroll-width="scrollWidth"
                :row-key-field-name="uniqueKey"
                :cell-span-option="cellMerge"
                :cell-style-option="cellStyle"
                :cell-selection-option="cellSelection"
                :event-custom-option="realEventOption"
                :radio-option="radioOption"
                :checkbox-option="checkboxOption"
                :row-style-option="vRowStyle"
                :expand-option="vExpandOption"
                border-y
                border-x
      />
      <div class="easy-pagination row justify-end">
        <ve-pagination v-if="showPagination && vPagination.total"
                       v-bind="vPagination"
                       @on-page-number-change="pageNumberChange"
                       @on-page-size-change="pageSizeChange"
        />
      </div>
    </template>
    <div v-show="!headers.length" class="text-grey text-center font-14" :style="{'line-height': maxHeight + 'px'}">{{ noData }}</div>
  </div>
</template>

<script>
import * as Util from './util'
import { uid } from 'quasar'
import Vue from 'vue'
import 'vue-easytable/libs/theme-default/index.css'
import VueEasytable, { VeLocale } from 'vue-easytable'
import zhCN from 'vue-easytable/libs/locale/lang/zh-CN'
Vue.use(VueEasytable)
VeLocale.use(zhCN)
export default {
  name: 'mg-easy-table',
  props: {
    tableData: { // 表格数据
      type: Array,
      required: true,
      default: () => []
    },
    headerData: { // 表头
      type: Array,
      required: true,
      default: () => []
    },
    noData: { // 无数据时的提示
      type: String,
      required: false,
      default: '暂无数据'
    },
    pagination: { // 分页
      type: Object,
      required: false,
      default: null
    },
    maxHeight: { // 表格最大高度
      type: Number,
      required: false,
      default: null
    },
    nodeKey: { // 对应headerData中的值字段名
      type: String,
      required: false
    },
    nodeLabel: { // 对应headerData中的描述字段名
      type: String,
      required: false
    },
    // 唯一标识字段(可不包含在headerData中，但必须在tableData中存在)
    // 启用 行选中、行展开、行单选、行多选、行点击高亮、虚拟滚动 功能时必须设置
    // ！！！！uniqueKey对应的字段值只能为字符串，不能为数字
    uniqueKey: {
      type: String,
      required: false,
      default: ''
    },
    scrollOption: { // 纵向滚动设置，配置项参考vue-easytable的virtual-scroll-option
      type: Object,
      required: false,
      default: () => {}
    },
    configs: { // 表格配置
      type: Object,
      required: false,
      default: () => ({
        sort: null, // 需排序的列 字符串数组
        // 筛选项配置  {fieldName1: { isMultiple: boolean }, fieldName2: null}  多层结构便于后续扩展其他字段
        // 当前只支持isMultiple, true 多选，false 单选; 也可直接定义为null，等同于{isMultiple： true}
        filter: null,
        frozen: { // 固定列
          width: null, // 滚动容器宽度scrollWidth  Number
          left: [], // 左侧固定的字段
          right: [] // 右侧固定的字段
        },
        // 超出单元格自动省略的字段 { fieldName1: { 设置项参考vue-easytable的ellipsis }, fieldName2: null, fieldName3: {} }
        // ？？？showTitle字段不生效
        ellipsis: null,
        width: null, // 指定列的宽度 { fieldName: 70 }
        // 自定义组件 { fieldName: 组件实例 }
        // 自定义组件内最好不要添加弹出框组件，避免冗余和性能问题，若有这样的需求，可调用componentCallback将数据传出，在外层组件中添加弹出框组件及逻辑
        component: null,
        componentCallback: null // 自定义组件回调函数（component属性有值时也可不定义）,用于在自定义组件内触发父组件逻辑（如刷新全局数据）
      })
    },
    finishFunc: { // created执行完后立即调用一次（此时数据基本处理完毕，便于用户作额外调整，比如处理缓存）
      type: Function,
      required: false
    },
    cellMerge: { // body合并，配置参考vue-easytable的cellSpanOption
      type: Object,
      required: false
    },
    cellStyle: { // 单元格样式，配置参考vue-easytabel的cellStyleOption
      type: Object,
      required: false
    },
    cellSelection: { // 是否开启单元格选中功能（enable为true，则鼠标点击时高亮单元格，并支持键盘上下左右选择）
      type: Object,
      required: false,
      default: () => ({
        enable: false
      })
    },
    // 事件配置:
    // bodyRowClick({row, rowIndex})  bodyCellClick({row,column,rowIndx})
    // headerRowClick({ rowIndx })  headerCellClick({ column,rowIndx })
    // footerRowClick({ row, rowIndex })  footerCellClick({ row,column,rowIndx })
    // 每一个配置项都为function
    eventOption: {
      type: Object,
      required: false
    },
    // 单选多选时互斥属性，若同时存在，优先考虑单选
    radioOption: { // radio选择，配置参考vue-easytable的radioOptipon
      type: Object,
      required: false
    },
    checkboxOption: { // checkbox选择，配置参考vue-easytable的checkboxOptipon
      type: Object,
      required: false
    },
    rowStyle: { // 行样式，配置项参考vue-easytable的rowStyleOption
      type: Object,
      required: false
    },
    // 行展开，配置项参考vue-easytable的expandOption，其中render字段忽略，改为component字段，对应自定义组件
    expandOption: {
      type: Object,
      required: false
    },
    updateHighlight: { // 是否支持数据更新时行高亮样式
      type: Boolean,
      required: false
    },
    highlightKey: { // 当前需要高亮的行唯一标识，对应uniqueKey字段值；若不指定，则默认首行高亮
      type: String,
      required: false
    }
  },
  data () {
    return {
      virtualScrollOption: Util.virtualScrollOption,
      defaultMaxHeight: 500, // 表格最大高度默认值，当启用虚拟滚动且用户未指定maxHeight时使用此值
      paginationHeight: 40, // 分页栏高度
      defaultPagination: Util.defaultPagination,

      defaultNodeKey: 'field',
      defaultNodeLabel: 'title',
      headers: [], // 表头
      datas: [], // 数据
      sortOption: {
        multipleSort: true, // 多字段排序
        sortChange: this.sortChange
      },
      filterOption: {}, // 过滤信息记录  { fieldName: filterList }
      filterSearch: {}, // 过滤选中项记录 { fieldName: 选中项列表(记录filterList中selected为true的值) }
      unwatchFilter: null, // 取消过滤项的监听

      defaultRowStyle: Util.defaultRowStyle,
      defaultExpandOption: Util.defaultExpandOption, // 展开行默认配置

      realEventOption: null, // 事件配置
      activeHighlightKey: '' // 当前高亮的行唯一标识
    }
  },
  computed: {
    showPagination () { // 是否显示分页
      return this.vPagination.enable
    },
    vPagination () { // 分页设置
      return Object.assign({}, this.defaultPagination, this.pagination || {})
    },
    maxH () { // 实际生效的表格最大高度
      let h = this.maxHeight || null // 防止用户设置为0
      this.showPagination && h && (h = Math.max(0, h - this.paginationHeight))
      return h || (this.virtualScrollOption.enable ? Math.max(this.defaultMaxHeight - this.paginationHeight, 0) : h)
    },
    scrollWidth () { // 滚动宽度
      return this.configs.frozen ? this.configs.frozen.width : null
    },
    frozenLeftColumns () { // 固定左列
      return this.configs.frozen ? (this.configs.frozen.left || []) : []
    },
    frozenrightColumns () { // 固定右列
      return this.configs.frozen ? (this.configs.frozen.right || []) : []
    },
    sortColumns () { // 需排序的列
      return this.configs.sort || []
    },
    filterColumns () { // 需筛选的字段
      return Object.keys(this.configs.filter || {})
    },
    ellipsisColumns () { // 超出省略的字段
      return Object.keys(this.configs.ellipsis || {})
    },
    col2width () { // 列对应宽度
      return this.configs.width || {}
    },
    customComp () { // 自定义列
      return Object.keys(this.configs.component || {})
    },
    showRadioColumn () { // 是否显示单选列
      return this.radioOption && Object.keys(this.radioOption).length
    },
    showCheckboxColumn () { // 是否显示多选列
      return this.checkboxOption && Object.keys(this.checkboxOption).length
    },
    vRowStyle () { // table样式
      return Object.assign({}, this.defaultRowStyle, this.rowStyle || {})
    },
    vExpandOption () { // 行展开配置项
      const keys = Object.keys(this.expandOption || {}), userExpandOption = this.expandOption || {}
      let tempMap = {}
      keys.forEach(k => {
        if (k === 'component') {
          tempMap['render'] = ({ row, column, rowIndex }, h) => {
            return h(userExpandOption[k], {props: {row, column, rowIndex}})
          }
        } else {
          tempMap[k] = userExpandOption[k]
        }
      })
      return Object.assign({}, this.defaultExpandOption, tempMap)
    },
    rowExpandable () { // 是否支持行展开
      return this.vExpandOption.expandable
    }
  },
  watch: {
    headerData () {
      this.tansformHeader()
    },
    tableData () { // 表格数据有更新
      this.afterDataChange()
    }
  },
  created () {
    // 表格事件解析
    this.parseEvent()
    // 更新高亮字段
    this.updateHighlight && (this.activeHighlightKey = this.highlightKey)
    // 虚拟滚动设置
    this.scrollOption && Object.keys(this.scrollOption).length && Object.assign(this.virtualScrollOption, this.scrollOption)
    if (this.filterColumns.length) {
      // 表头计算
      this.filterColumns.forEach(f => {
        this.$set(this.filterOption, f, null)
        this.$set(this.filterSearch, f, [])
      })
      this.unwatchFilter = this.$watch('filterSearch', this.watchFilterSearch, { deep: true })
    }
    this.tansformHeader()
    this.afterDataChange()

    typeof this.finishFunc === 'function' && this.finishFunc(this)
  },
  beforeDestroy () { // 实例销毁时，取消监听事件
    typeof this.unwatchFilter === 'function' && this.unwatchFilter()
    this.headers = []
    this.datas = []
  },
  methods: {
    parseEvent () { // 合并用户事件到组件原有事件结构中
      const eventNames = Object.keys(this.eventOption || {})
      if (!eventNames.length) {
        return
      }
      const eventObj = this.eventOption
      this.realEventOption = {
        bodyRowEvents: this.eventWraper(eventObj, 'bodyRow'),
        bodyCellEvents: this.eventWraper(eventObj, 'bodyCell'),
        headerRowEvents: this.eventWraper(eventObj, 'headerRow'),
        headerCellEvents: this.eventWraper(eventObj, 'headerCell'),
        footerRowEvents: this.eventWraper(eventObj, 'footerRow'),
        footerCellEvents: this.eventWraper(eventObj, 'footerCell')
      }
    },
    eventWraper (eventObj, eventPrefix) {
      const self = this
      const clickHandler = eventObj[eventPrefix + 'Click']
      return (param) => {
        return {
          click: typeof clickHandler === 'function' ? event => {
            if (eventPrefix.startsWith('body')) { // 触发表格body的行点击或列点击,记录当前行的唯一标识
              this.activeHighlightKey = param['row'][self.uniqueKey]
            }
            clickHandler(param) // 执行用户定义的事件回调
          } : null
          // 可在此处继续增加其他事件
        }
      }
    },
    tansformHeader () { // 表头字段key替换
      if (!this.headerData.length) {
        return
      }
      let headerList = []
      if ((this.nodeKey && this.nodeKey !== this.defaultNodeKey)
        || (this.nodeLabel && this.nodeLabel !== this.defaultNodeLabel)) {
        Util.transformListByField(this.headerData, headerList, this.nodeKey || this.defaultNodeKey,
          this.nodeLabel || this.defaultNodeLabel, this.defaultNodeKey, this.defaultNodeLabel)
      } else {
        headerList = this.headerData.slice(0)
      }
      Util.traverseList(headerList, this.initColumn)
      this.setSelectAndFrozen(headerList)
      this.headers = headerList
    },
    afterDataChange () { // 数据改变时，重置表头数据和表格数据
      if (this.headers.length && this.filterColumns.length) {
        this.setFilterList()
        Util.traverseList(this.headers, this.setFilterCol)
      }
      this.datas = this.tableData.slice(0)
      // 默认高亮第一行
      if (this.updateHighlight && this.datas.length) {
        let highlightKey = this.activeHighlightKey
        const _uniqueKey = this.uniqueKey
        const targetItem = this.datas.find(_ => String(_[_uniqueKey]) === String(highlightKey))
        !targetItem && (this.activeHighlightKey = highlightKey = this.datas[0][_uniqueKey])
        this.setHighlightRow(highlightKey)
      }
    },
    setHighlightRow (uniqueKey) { // 指定行高亮  uniqueKey：行唯一标识
      this.$nextTick(() => {
        const $table = this.$refs['mg-easy-table-instance']
        if (!$table || !($table.$children instanceof Array)) {
          return
        }
        const subList = $table.$children
        for (let i = 0; i < subList.length; i++) {
          const firstCls = subList[i].$el.classList[0] || ''
          if (firstCls === 've-table-body') {
            subList[i].highlightRowKey = uniqueKey // 方式一
            // subList[i].rowClick({rowData: this.datas[0], rowIndex: 0}) // 方式二
            break
          }
        }
      })
    },
    watchFilterSearch (searchData) { // 监听过滤操作
      const colfields = this.filterColumns
      this.datas = this.tableData.filter(d => {
        return colfields.every(f => {
          const list = searchData[f] || []
          return list.length === 0 || list.includes(d[f])
        })
      })
    },
    setFilterList () { // 记录过滤项列表
      let map = {}
      const filterCols = this.filterColumns
      filterCols.forEach(_ => (map[_] = new Set))
      this.tableData.forEach(d => {
        filterCols.forEach(f => map[f].add(d[f]))
      })
      for (let f in map) {
        map[f] = Array.from(map[f]).map(_ => ({value: _, label: _, selected: false}))
      }
      Object.assign(this.filterOption, map)
    },
    setFilterCol (col) { // 设置筛选字段
      if (!col) {
        return
      }
      const f = col[this.defaultNodeKey]
      if (f && this.filterColumns.includes(f)) {
        const isMultiple = (this.configs.filter[f] && 'isMultiple' in this.configs.filter[f])
          ? this.configs.filter[f]['isMultiple'] : true
        col['filter'] = {
          filterConfirm: this.filterConfirm(f),
          filterReset: this.filterConfirm(f),
          filterList: this.filterOption[f],
          isMultiple,
          maxHeight: 300
        }
      }
    },
    initColumn (col) { // 加上排序字段、省略字段等
      if (!col) {
        return
      }
      !col.hasOwnProperty('align') && (col['align'] = 'center')
      const f = col[this.defaultNodeKey]
      if (f) {
        !col.hasOwnProperty('key') && (col['key'] = f)
        // 设置字段排序，默认无排序
        if (this.sortColumns.includes(f) && !col.hasOwnProperty('sortBy')) {
          col['sortBy'] = ''
        }
        // 设置省略字段
        if (this.ellipsisColumns.includes(f)) {
          col['ellipsis'] = this.configs.ellipsis[f] || {}
        }
        // 设置自定义列
        if (this.customComp.includes(f)) {
          const componentInstance = this.configs.component[f]
          const callback = this.configs.componentCallback
          col['renderBodyCell'] = function ({ row, column, rowIndex }, h) {
            return h(componentInstance, {props: {row, column, rowIndex, callback, field: f}})
          }
        }
        // 设置宽度
        this.col2width.hasOwnProperty(f) && (col['width'] = this.col2width[f])
      }
    },
    setSelectAndFrozen (list = []) {
      // 设置固定列（复杂表头中，只需在顶层列中设置）
      const leftCols = this.frozenLeftColumns, rightCols = this.frozenrightColumns
      if (leftCols.length || rightCols.length) {
        list.forEach(h => {
          if (Util.findTarget(h, leftCols)) {
            h['fixed'] = 'left'
          } else if (Util.findTarget(h, rightCols)) {
            h['fixed'] = 'right'
          }
        })
      }
      // 添加单选、多选、行展开
      const colType = this.showRadioColumn ? 'radio'
        : (this.showCheckboxColumn ? 'checkbox' : (this.rowExpandable ? 'expand' : ''))
      if (colType) {
        // 若第一列固定，则radio或checkbox列也需固定
        const fixed = list[0]['fixed']
        list.unshift({
          [this.defaultNodeKey]: '',
          key: uid(),
          type: colType,
          [this.defaultNodeLabel]: '',
          width: 50,
          align: 'center',
          fixed
        })
      }
    },
    sortChange (params) { // 默认排序规则
      const fields = Object.keys(params || {})
      if (!fields.length) {
        return
      }

      const data = this.datas.slice(0)
      fields.forEach(f => data.sort((a, b) => Util.sortByField(a, b, f, params[f])))

      this.datas = data
    },
    filterConfirm (f) { // 记录当前选中项，触发filterSearch的watch事件
      const self = this
      return filterList => {
        self.filterSearch[f] = filterList.filter((x) => x.selected).map((x) => x.label)
      }
    },
    pageNumberChange (pageIndex) {
      this.$emit('page-change', {
        pageIndex,
        pageSize: this.pagination.pageSize
      })
    },
    pageSizeChange (pageSize) {
      this.$emit('page-change', {
        pageIndex: 1,
        pageSize
      })
    }
  }
}
</script>

<style lang="stylus">
@import "index.styl"
</style>
